// 版权所有2011 Go作者。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

// 从ASTs文件中提取示例函数。

package doc

import (
	"go/ast"
	"go/token"
	"internal/lazyregexp"
	"path"
	"sort"
	"strconv"
	"strings"
	"unicode"
	"unicode/utf8"
)

// 示例表示在测试源文件中找到的示例函数。
type Example struct {
	Name        string // 示例项目名称（包括可选后缀）
	Suffix      string // 示例后缀，不带前导“\”（仅由NewFromFiles填充）
	Doc         string // 示例函数文档字符串
	Code        ast.Node
	Play        *ast.File // 示例的完整程序版本
	Comments    []*ast.CommentGroup
	Output      string // 预期产量
	Unordered   bool
	EmptyOutput bool // 期望空输出
	Order       int  // 原始源代码顺序
}

// Examples返回testFiles中的示例，按名称字段排序。
// Order字段记录遇到示例的顺序。
// 直接调用示例时不填充后缀字段，而是
// 对于在_test.go文件中找到的示例，仅由NewFromFiles填充。
// None
// 可播放的示例必须位于名称以“\u test”结尾的包中。
// 其中一个例子是“可玩”（游戏场为非零）
// 情况：
// -示例函数是自包含的：该函数仅引用
// 来自其他包的标识符（或预先声明的标识符，例如
// “int”）并且测试文件不包括点导入。
// -整个测试文件就是一个示例：该文件正好包含一个测试文件
// 示例函数、零测试或基准测试函数，以及至少一个
// 顶级函数、类型、变量或常量声明其他
// 而不是示例函数。
func Examples(testFiles ...*ast.File) []*Example {
	var list []*Example
	for _, file := range testFiles {
		hasTests := false // 文件包含测试或基准测试
		numDecl := 0      // 文件中的非导入声明数
		var flist []*Example
		for _, decl := range file.Decls {
			if g, ok := decl.(*ast.GenDecl); ok && g.Tok != token.IMPORT {
				numDecl++
				continue
			}
			f, ok := decl.(*ast.FuncDecl)
			if !ok || f.Recv != nil {
				continue
			}
			numDecl++
			name := f.Name.Name
			if isTest(name, "Test") || isTest(name, "Benchmark") {
				hasTests = true
				continue
			}
			if !isTest(name, "Example") {
				continue
			}
			if params := f.Type.Params; len(params.List) != 0 {
				continue // 函数有参数；这不是一个有效的例子
			}
			if f.Body == nil { // ast.File.Body无取消引用（参见问题28044）
				continue
			}
			var doc string
			if f.Doc != nil {
				doc = f.Doc.Text()
			}
			output, unordered, hasOutput := exampleOutput(f.Body, file.Comments)
			flist = append(flist, &Example{
				Name:        name[len("Example"):],
				Doc:         doc,
				Code:        f.Body,
				Play:        playExample(file, f),
				Comments:    file.Comments,
				Output:      output,
				Unordered:   unordered,
				EmptyOutput: output == "" && hasOutput,
				Order:       len(flist),
			})
		}
		if !hasTests && numDecl > 1 && len(flist) == 1 {
			// 如果此文件只有一个示例函数，则
			// 其他顶级声明，并且没有测试或
			// 基准测试，以整个文件为例。
			flist[0].Code = file
			flist[0].Play = playExampleFile(file)
		}
		list = append(list, flist...)
	}
	// 按名称排序
	sort.Slice(list, func(i, j int) bool {
		return list[i].Name < list[j].Name
	})
	return list
}

var outputPrefix = lazyregexp.New(`(?i)^[[:space:]]*(unordered )?output:`)

// 提取预期输出以及是否存在有效的输出注释
func exampleOutput(b *ast.BlockStmt, comments []*ast.CommentGroup) (output string, unordered, ok bool) {
	if _, last := lastComment(b, comments); last != nil {
		// 测试它是否以正确的前缀开头
		text := last.Text()
		if loc := outputPrefix.FindStringSubmatchIndex(text); loc != nil {
			if loc[2] != -1 {
				unordered = true
			}
			text = text[loc[1]:]
			// 删除零个或多个空格，后跟\n或单个空格。
			text = strings.TrimLeft(text, " ")
			if len(text) > 0 && text[0] == '\n' {
				text = text[1:]
			}
			return text, unordered, true
		}
	}
	return "", false, false // 没有找到合适的评论
}

// isTest告诉您名称看起来是测试、示例还是基准。
// 如果测试后有一个字符不是一个字符，那么这就是一个测试
// 小写字母。（我们不想要暴躁。）
func isTest(name, prefix string) bool {
	if !strings.HasPrefix(name, prefix) {
		return false
	}
	if len(name) == len(prefix) { // “测试”没问题
		return true
	}
	rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
	return !unicode.IsLower(rune)
}

// playExample根据提供的
// 以提供的函数体作为主体的文件。
func playExample(file *ast.File, f *ast.FuncDecl) *ast.File {
	body := f.Body

	if !strings.HasSuffix(file.Name.Name, "_test") {
		// 我们不支持作为本报告一部分的示例
		// 更大的包装（尚未）。
		return nil
	}

	// 收集文件中的顶级声明。
	topDecls := make(map[*ast.Object]ast.Decl)
	typMethods := make(map[string][]ast.Decl)

	for _, decl := range file.Decls {
		switch d := decl.(type) {
		case *ast.FuncDecl:
			if d.Recv == nil {
				topDecls[d.Name.Obj] = d
			} else {
				if len(d.Recv.List) == 1 {
					t := d.Recv.List[0].Type
					tname, _ := baseTypeName(t)
					typMethods[tname] = append(typMethods[tname], d)
				}
			}
		case *ast.GenDecl:
			for _, spec := range d.Specs {
				switch s := spec.(type) {
				case *ast.TypeSpec:
					topDecls[s.Name.Obj] = d
				case *ast.ValueSpec:
					for _, name := range s.Names {
						topDecls[name.Obj] = d
					}
				}
			}
		}
	}

	// 查找未解析的标识符和顶级声明的使用。
	unresolved := make(map[string]bool)
	var depDecls []ast.Decl
	hasDepDecls := make(map[ast.Decl]bool)

	var inspectFunc func(ast.Node) bool
	inspectFunc = func(n ast.Node) bool {
		switch e := n.(type) {
		case *ast.Ident:
			if e.Obj == nil && e.Name != "_" {
				unresolved[e.Name] = true
			} else if d := topDecls[e.Obj]; d != nil {
				if !hasDepDecls[d] {
					hasDepDecls[d] = true
					depDecls = append(depDecls, d)
				}
			}
			return true
		case *ast.SelectorExpr:
			// 对于选择器表达式，仅检查左侧。
			// （对于像fmt.Println这样的表达式，只需将“fmt”添加到
			// 未解析名称集，而不是“Println”。）
			ast.Inspect(e.X, inspectFunc)
			return false
		case *ast.KeyValueExpr:
			// 对于键值表达式，仅检查值
			// 因为密钥应该由
			// 复合文字。
			ast.Inspect(e.Value, inspectFunc)
			return false
		}
		return true
	}
	ast.Inspect(body, inspectFunc)
	for i := 0; i < len(depDecls); i++ {
		switch d := depDecls[i].(type) {
		case *ast.FuncDecl:
			// 检查参数类型和结果。见#28492。
			if d.Type.Params != nil {
				for _, p := range d.Type.Params.List {
					ast.Inspect(p.Type, inspectFunc)
				}
			}
			if d.Type.Results != nil {
				for _, r := range d.Type.Results.List {
					ast.Inspect(r.Type, inspectFunc)
				}
			}

			// 函数可能没有主体。见#42706。
			if d.Body != nil {
				ast.Inspect(d.Body, inspectFunc)
			}
		case *ast.GenDecl:
			for _, spec := range d.Specs {
				switch s := spec.(type) {
				case *ast.TypeSpec:
					ast.Inspect(s.Type, inspectFunc)

					depDecls = append(depDecls, typMethods[s.Name.Name]...)
				case *ast.ValueSpec:
					if s.Type != nil {
						ast.Inspect(s.Type, inspectFunc)
					}
					for _, val := range s.Values {
						ast.Inspect(val, inspectFunc)
					}
				}
			}
		}
	}

	// 从未解析列表中删除预先声明的标识符。
	for n := range unresolved {
		if predeclaredTypes[n] || predeclaredConstants[n] || predeclaredFuncs[n] {
			delete(unresolved, n)
		}
	}

	// 使用未解析的标识符确定此服务器使用的导入
	// 实例启发式假设包名与基本导入匹配
	// 不重命名导入的路径（大多数情况下应该足够好）。
	namedImports := make(map[string]string) // [名称]路径
	var blankImports []ast.Spec             // _进口
	for _, s := range file.Imports {
		p, err := strconv.Unquote(s.Path.Value)
		if err != nil {
			continue
		}
		if p == "syscall/js" {
			// 我们不支持导入syscall/js的示例，
			// 因为syscall/js包在游乐场中不可用。
			return nil
		}
		n := path.Base(p)
		if s.Name != nil {
			n = s.Name.Name
			switch n {
			case "_":
				blankImports = append(blankImports, s)
				continue
			case ".":
				// 我们还不能解决点导入问题。
				return nil
			}
		}
		if unresolved[n] {
			namedImports[n] = p
			delete(unresolved, n)
		}
	}

	// 如果还有其他未解析的标识符，请放弃，因为
	// 合成文件将不会生成。
	if len(unresolved) > 0 {
		return nil
	}

	// 包括属于空白导入的文档。
	var comments []*ast.CommentGroup
	for _, s := range blankImports {
		if c := s.(*ast.ImportSpec).Doc; c != nil {
			comments = append(comments, c)
		}
	}

	// 包括函数体内部的注释。
	for _, c := range file.Comments {
		if body.Pos() <= c.Pos() && c.End() <= body.End() {
			comments = append(comments, c)
		}
	}

	// 剥离“输出：”或“无序输出：”注释并调整正文
	// 结束位置。
	body, comments = stripOutputComment(body, comments)

	// 包括属于从属声明的文档。
	for _, d := range depDecls {
		switch d := d.(type) {
		case *ast.GenDecl:
			if d.Doc != nil {
				comments = append(comments, d.Doc)
			}
		case *ast.FuncDecl:
			if d.Doc != nil {
				comments = append(comments, d.Doc)
			}
		}
	}

	// 综合进口报关单。
	importDecl := &ast.GenDecl{
		Tok:    token.IMPORT,
		Lparen: 1, // 需要非零Lparen和Rparen，以便打印机
		Rparen: 1, // 将其视为已分解的导入。
	}
	for n, p := range namedImports {
		s := &ast.ImportSpec{Path: &ast.BasicLit{Value: strconv.Quote(p)}}
		if path.Base(p) != n {
			s.Name = ast.NewIdent(n)
		}
		importDecl.Specs = append(importDecl.Specs, s)
	}
	importDecl.Specs = append(importDecl.Specs, blankImports...)

	// 综合主要功能。
	funcDecl := &ast.FuncDecl{
		Name: ast.NewIdent("main"),
		Type: f.Type,
		Body: body,
	}

	decls := make([]ast.Decl, 0, 2+len(depDecls))
	decls = append(decls, importDecl)
	decls = append(decls, depDecls...)
	decls = append(decls, funcDecl)

	sort.Slice(decls, func(i, j int) bool {
		return decls[i].Pos() < decls[j].Pos()
	})

	sort.Slice(comments, func(i, j int) bool {
		return comments[i].Pos() < comments[j].Pos()
	})

	// 合成文件。
	return &ast.File{
		Name:     ast.NewIdent("main"),
		Decls:    decls,
		Comments: comments,
	}
}

// playExampleFile以整个文件为例，合成一个新的*ast.file
// 这样，示例就是包main中的函数main。
func playExampleFile(file *ast.File) *ast.File {
	// 删除版权注释（如果存在）。
	comments := file.Comments
	if len(comments) > 0 && strings.HasPrefix(comments[0].Text(), "Copyright") {
		comments = comments[1:]
	}

	// 复制声明片，将ExampleX函数重写为main。
	var decls []ast.Decl
	for _, d := range file.Decls {
		if f, ok := d.(*ast.FuncDecl); ok && isTest(f.Name.Name, "Example") {
			// 复制functdecl，因为它可能在其他地方使用。
			newF := *f
			newF.Name = ast.NewIdent("main")
			newF.Body, comments = stripOutputComment(f.Body, comments)
			d = &newF
		}
		decls = append(decls, d)
	}

	// 复制该文件，因为它可能在其他地方使用。
	f := *file
	f.Name = ast.NewIdent("main")
	f.Decls = decls
	f.Comments = comments
	return &f
}

// stripOutputComment查找并删除“输出：”或“无序输出”：
// 来自主体和注释的注释，并调整主体块的结束位置。
func stripOutputComment(body *ast.BlockStmt, comments []*ast.CommentGroup) (*ast.BlockStmt, []*ast.CommentGroup) {
	// 如果没有“输出：”或“无序输出：”注释，则不执行任何操作。
	i, last := lastComment(body, comments)
	if last == nil || !outputPrefix.MatchString(last.Text()) {
		return body, comments
	}

	// 复制正文和注释，因为原件可在其他地方使用。
	newBody := &ast.BlockStmt{
		Lbrace: body.Lbrace,
		List:   body.List,
		Rbrace: last.Pos(),
	}
	newComments := make([]*ast.CommentGroup, len(comments)-1)
	copy(newComments, comments[:i])
	copy(newComments[i:], comments[i+1:])
	return newBody, newComments
}

// lastComment返回所提供块内的最后一条注释。
func lastComment(b *ast.BlockStmt, c []*ast.CommentGroup) (i int, last *ast.CommentGroup) {
	if b == nil {
		return
	}
	pos, end := b.Pos(), b.End()
	for j, cg := range c {
		if cg.Pos() < pos {
			continue
		}
		if cg.End() > end {
			break
		}
		i, last = j, cg
	}
	return
}

// classifyExamples对示例进行分类并将其分配到examples字段
// 与示例关联的相关Func、类型或包的。
// None
// 在某些情况下，分类过程不明确：
// None
// -示例Foo\u Bar与名为Foo\u Bar的类型匹配
// 或者一个名为Foo.Bar的方法。
// -示例Foo\u bar与名为Foo\u bar的类型匹配
// 或Foo（带有“bar”后缀）。
// None
// 名称格式错误的示例与任何内容都没有关联。
// None
func classifyExamples(p *Package, examples []*Example) {
	if len(examples) == 0 {
		return
	}

	// 将函数、类型和方法的名称映射到示例列表。
	ids := make(map[string]*[]*Example)
	ids[""] = &p.Examples // 包级示例的名称为空
	for _, f := range p.Funcs {
		if !token.IsExported(f.Name) {
			continue
		}
		ids[f.Name] = &f.Examples
	}
	for _, t := range p.Types {
		if !token.IsExported(t.Name) {
			continue
		}
		ids[t.Name] = &t.Examples
		for _, f := range t.Funcs {
			if !token.IsExported(f.Name) {
				continue
			}
			ids[f.Name] = &f.Examples
		}
		for _, m := range t.Methods {
			if !token.IsExported(m.Name) {
				continue
			}
			ids[strings.TrimPrefix(m.Recv, "*")+"_"+m.Name] = &m.Examples
		}
	}

	// 使用关联的func、type或method对每个示例进行分组。
	for _, ex := range examples {
		// 考虑后缀的所有可能的分裂点
		// 从字符串末尾开始（无后缀大小写），
		// 然后尝试所有包含“\u1”字符的位置。
		// None
		// 在第一次成功匹配时进行关联。
		// 将跳过名称格式错误且不匹配的示例。
		for i := len(ex.Name); i >= 0; i = strings.LastIndexByte(ex.Name[:i], '_') {
			prefix, suffix, ok := splitExampleName(ex.Name, i)
			if !ok {
				continue
			}
			exs, ok := ids[prefix]
			if !ok {
				continue
			}
			ex.Suffix = suffix
			*exs = append(*exs, ex)
			break
		}
	}

	// 根据用户指定的后缀名对示例列表进行排序。
	for _, exs := range ids {
		sort.Slice((*exs), func(i, j int) bool {
			return (*exs)[i].Suffix < (*exs)[j].Suffix
		})
	}
}

// splitExampleName尝试在索引i处拆分示例名称s，
// 并报告是否生成有效拆分。后缀可能是
// 缺席的否则，它必须以小写字母和
// 在前面加上‘‘’。
// None
// i==len或s[i]='.'中的一个必须为真。
func splitExampleName(s string, i int) (prefix, suffix string, ok bool) {
	if i == len(s) {
		return s, "", true
	}
	if i == len(s)-1 {
		return "", "", false
	}
	prefix, suffix = s[:i], s[i+1:]
	return prefix, suffix, isExampleSuffix(suffix)
}

func isExampleSuffix(s string) bool {
	r, size := utf8.DecodeRuneInString(s)
	return size > 0 && unicode.IsLower(r)
}
